package dev.marsh.sniper;

import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Vibrator;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.android.Utils;
import org.opencv.core.Mat;

import dev.marsh.sniper.ar.tracker.JavaDetectionTracker;
import dev.marsh.sniper.core.BuildConfig;
import dev.marsh.sniper.core.R;
import dev.marsh.sniper.core.SniperView;
import io.reactivex.Observable;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;

/**
 * 从相机识别图片
 */
public abstract class ScanActivity extends AppCompatActivity implements CameraBridgeViewBase.CvCameraViewListener2 {
    private static final String TAG = ScanActivity.class.getSimpleName();

    private SniperView mSniperView;
    private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            switch (status) {
                case LoaderCallbackInterface.SUCCESS: {
                    if (BuildConfig.DEBUG) {
                        Log.i(TAG, "OpenCV loaded successfully");
                    }
                    mSniperView.startSniper();
                    if (mTracker == null) {
                        initSampleData();
                    }
                }
                break;
                default: {
                    super.onManagerConnected(status);
                }
                break;
            }
        }
    };

    private JavaDetectionTracker mTracker;

    private void initSampleData() {
        Observable.just(0)
                .map(integer -> new JavaDetectionTracker(getApplicationContext()))
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())

                .subscribe(next -> {
                    if (BuildConfig.DEBUG) {
                        Log.d(TAG, next.toString());
                    }
                    mTracker = next;
                }, error -> {
                    if (BuildConfig.DEBUG) {
                        error.printStackTrace();
                    }
                });
    }

    private CameraFrameListener mListener;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scan);
        setSupportActionBar((Toolbar) findViewById(R.id.toolbar));

        mSniperView = (SniperView) findViewById(R.id.go_sniper);
        mSniperView.setDelegate(this);

        initProcess();
    }

    private void initProcess() {
        Observable.create((ObservableOnSubscribe<Mat>) e -> {
            if (mListener == null) {
                mListener = e::onNext;
            }
        })
                .map(new Function<Mat, Boolean>() {
                    int count = 0;

                    @Override
                    public Boolean apply(Mat mat) throws Exception {
                        if (mTracker == null) {
                            return false;
                        }

                        if (count % 30 == 0) {
                            Bitmap bmp = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888);
                            Utils.matToBitmap(mat, bmp);

                            int x = 0;
                            int y = 0;
                            int width = bmp.getWidth();
                            int height = bmp.getHeight();
                            int realHigh = width;

                            Rect rect = mSniperView.getScanBoxView().getScanBoxAreaRect(realHigh);
                            if (rect != null && rect.left + rect.width() <= height && rect.top + rect.height() <= width) {
                                //noinspection SuspiciousNameCombination
                                x = rect.top;
                                //noinspection SuspiciousNameCombination
                                y = rect.left;
                                width = rect.height();
                                height = rect.width();
                            }

                            Matrix matrix = new Matrix();
                            matrix.preRotate(90);
                            bmp = Bitmap.createBitmap(bmp, x, y, width, height, matrix, true);

                            Mat t = new Mat();
                            Utils.bitmapToMat(bmp, t);

                            return mTracker.track(t);
                        }

                        if (count == Integer.MAX_VALUE) {
                            count = 0;
                        }
                        count++;

                        return false;
                    }
                })
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Boolean>() {
                    private Disposable mDisposable;

                    @Override
                    public void onSubscribe(Disposable d) {
                        mDisposable = d;
                    }

                    @Override
                    public void onNext(Boolean value) {
                        if (value) {
                            mDisposable.dispose();
                            showToast(true);
                        }
                    }

                    @Override
                    public void onError(Throwable e) {
                        if (BuildConfig.DEBUG) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onComplete() {
                        // nothing...
                    }
                });
    }

    @Override
    public void onResume() {
        super.onResume();
        if (!OpenCVLoader.initDebug()) {
            if (BuildConfig.DEBUG) {
                Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization");
            }
            OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_2_0, this, mLoaderCallback);
        } else {
            if (BuildConfig.DEBUG) {
                Log.d(TAG, "OpenCV library found inside package. Using it!");
            }
            mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
        }
    }

    @Override
    protected void onPause() {
        if (mSniperView != null) {
            mSniperView.stopSniper();
        }
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        if (mSniperView != null) {
            mSniperView.onDestroy();
        }
        super.onDestroy();
    }

    public void onClick(View v) {
        int id = v.getId();
        if (id == R.id.start) {
            mSniperView.startSpot();
        } else if (id == R.id.stop) {
            mSniperView.stopSpot();
        } else if (id == R.id.show) {
            mSniperView.showScanRect();
        } else if (id == R.id.hide) {
            mSniperView.hiddenScanRect();
        }
    }

    @Override
    public void onCameraViewStarted(int width, int height) {

    }

    @Override
    public void onCameraViewStopped() {

    }

    @Override
    public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
        if (mListener != null) {
            mListener.onCameraFrame(inputFrame.rgba());
        }

        return null;
    }

    private void showToast(boolean success) {
        Toast.makeText(getApplicationContext(), success ? R.string.sniper_success : R.string.sniper_failure, Toast.LENGTH_SHORT).show();
        if (success) {
            vibrate();
        }
        result(success);
    }

    private void vibrate() {
        Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
        vibrator.vibrate(200);
    }

    public interface CameraFrameListener {
        void onCameraFrame(Mat mat);
    }

    protected abstract void result(boolean success);
}